package classfile

import "fmt"

type ClassFile struct {
	magic             uint32 // default is 0xCAFEBABE
	minorVersion      uint16
	majorVersion      uint16
	constantPoolCount uint16
	constantPool      ConstantPool
	accessFlags       uint16 //
	thisClass         uint16
	superClass        uint16
	interfaceCount    uint16
	interfaces        []uint16
	fieldsCount       uint16
	fields            []*MemberInfo
	methodCount       uint16
	methods           []*MemberInfo
	attributeCount    uint16
	attributes        []AttributeInfo
}

// Parse
// Input: []byte, class file
// Output: ClassFile struct
func Parse(classData []byte) (classfile *ClassFile, err error) {
	defer func() {
		if r := recover(); r != nil {
			var ok bool
			err, ok = r.(error)
			if !ok {
				err = fmt.Errorf("%v", r)
			}
		}
	}()
	classReader := &ClassReader{classData}
	classfile = &ClassFile{}
	classfile.read(classReader)
	return
}

func (self *ClassFile) read(reader *ClassReader) {
	self.readAndCheckMagic(reader)
	self.readAndCheckVersion(reader)
	self.constantPoolCount = reader.readUint16()
	self.constantPool = readConstantPool(reader, self.constantPoolCount)
	self.accessFlags = reader.readUint16()
	self.thisClass = reader.readUint16()
	self.superClass = reader.readUint16()
	self.interfaceCount = reader.readUint16()
	self.interfaces = reader.readUint16s(self.interfaceCount)
	self.fieldsCount = reader.readUint16()
	self.fields = readMembers(reader, self.fieldsCount, self.constantPool)
	self.methodCount = reader.readUint16()
	self.methods = readMembers(reader, self.methodCount, self.constantPool)
	self.attributeCount = reader.readUint16()
	self.attributes = readAttributes(reader, self.attributeCount, self.constantPool)
}

func (self *ClassFile) readAndCheckMagic(reader *ClassReader) {
	magic := reader.readUint32()
	if magic != 0xCAFEBABE {
		panic("java.lang.ClassFormatError: magic!")
	}
}

func (self *ClassFile) readAndCheckVersion(reader *ClassReader) {
	self.minorVersion = reader.readUint16()
	self.majorVersion = reader.readUint16()
	switch self.majorVersion {
	case 45:
		return
	case 46, 47, 48, 49, 50, 51, 52:
		if self.minorVersion == 0 {
			return
		}
	}
	panic("java.lang.UnsupportedClassVersionError!")
}

func (self *ClassFile) MajorVersion() uint16 {
	return self.majorVersion
}

func (self *ClassFile) MinorVersion() uint16 {
	return self.minorVersion
}

func (self *ClassFile) ConstantPoolCount() uint16 {
	return self.constantPoolCount
}

func (self *ClassFile) ConstantPool() ConstantPool {
	return self.constantPool
}

func (self *ClassFile) AccessFlags() uint16 {
	return self.accessFlags
}

func (self *ClassFile) ThisClass() string {
	val := self.constantPool.getConstantInfo(self.thisClass)
	v, ok := val.(*ConstantClassInfo)
	if ok {
		return self.constantPool.getUtf8(v.nameIndex)
	}
	panic("Type Error")
}
func (self *ClassFile) SuperClass() string {
	val := self.constantPool.getConstantInfo(self.superClass)
	v, ok := val.(*ConstantClassInfo)
	if ok {
		return self.constantPool.getUtf8(v.nameIndex)
	}
	panic("Type Error")
}

func (self *ClassFile) InterfaceNames() []string {
	ret := make([]string, 0)
	for _, index := range self.interfaces {
		ret = append(ret, self.constantPool.getClassName(index))
	}
	return ret
}

func (self *ClassFile) SuperClassName() string {
	if self.superClass > 0 {
		return self.constantPool.getClassName(self.superClass)
	}
	// java/lang/Object没有superClass
	return ""
}

func (self *ClassFile) Fields() []*MemberInfo {
	return self.fields
}

func (self *ClassFile) Methods() []*MemberInfo {
	return self.methods
}

func (self *ClassFile) ClassName() string {
	return self.constantPool.getClassName(self.thisClass)
}

func (self *ClassFile) SourceFileAttribute() *SourceFileAttribute {
	attrs := self.attributes
	for _, attr := range attrs {
		switch attr.(type) {
		case *SourceFileAttribute:
			return attr.(*SourceFileAttribute)
		}
	}
	return nil
}
